#include "simulation.h"

PriorityNPSimulation::PriorityNPSimulation(int argc, char **argv) : Simulation() {

	kSeed_arrival = time(NULL);				//seed for the generating the interarrival times
	kSeed_service = time(NULL) + 10467;		//seed for the generating the service times

	//command line inputs and validations
	lambda = atof(argv[1]);		//0 < lambda < 1
	if (lambda > 1) {
		cout << "Invalid value of lambda. lambda must be in (0, 1)" << endl;
		return;
	}
	mu = 1;						//mu = 1 always
	kcpu = atoi(argv[2]);
	kio = atoi(argv[3]);
	if (kcpu <= 0) {
		cout << "Invalid value of K_CPU" << endl;
		return;
	}
	if (l == 1 && kio <= 0) {
		cout << " Invalid value of K_IO" << endl;
		return;
	}
	c = atoi(argv[4]);
	if (c <= 0) {
		cout << "Invalid value of C. C must be greater than 0" << endl;
		return;
	}
	l = atoi(argv[5]);
	if (! (l == 0 || l == 1)) {
		cout << "Invalid value of L" << endl;
		exit(0);
	}
	m = atoi(argv[6]);
	if (m < 0 || m > 5) {
		cout << "Invalid value of M" << endl;
		exit(0);
	}

	Ta = 200.0;
	Ts = 100.0;
	te = 1<<30;
	n = 0;
	t1 = expntl(1/lambda, kSeed_arrival);
	t2 = te;
	sim_time = 0.0;	
	kSeed_selector = time(NULL) - 9502;

	serve_count = 0;
	master_clock = 0;

	arrival_count = 0;
	departure_count = 0;
	total_arrival_count = 0;
	total_departure_count = 0;

	capacity[0] = 0;
	capacity[1] = 0;
	capacity[2] = 0;
	capacity[3] = 0;

	queue_avg_service_time[0] = 0;
	queue_avg_service_time[1] = 0;
	queue_avg_service_time[2] = 0;
	queue_avg_service_time[3] = 0;

	queue_avg_waiting_time[0] = 0;
	queue_avg_waiting_time[1] = 0;
	queue_avg_waiting_time[2] = 0;
	queue_avg_waiting_time[3] = 0;
	//initiate the arrival of the first customer, generate the corresponding event,
	//add it to the event list and add the customer to the customer_list
	//set simulation time
	
	Ts = expntl(1/mu, kSeed_service);
	Customer *customer = new Customer(total_arrival_count++, t1, Ts, 0.0, 0.0, false, false);
	init_customer_queue(customer);
	EventNode *next_event = new EventNode('A', customer, t1);
	event_list_add(next_event);
	sim_time = t1;
	customer_list.push_back(customer);
}

//schedule a departure event for a given customer
void PriorityNPSimulation::schedule_departure(Customer *customer) {
//	cout << "schedule departure for " << customer->id_ << endl;
	//Ts = expntl(1/mu, kSeed_service);
	//customer->service_time_ = Ts;
	if (customer->queue_number_ == 0) {
		EventNode *current_departure = new EventNode('D', customer, sim_time + customer->service_time_);
		event_list_add(current_departure);
		//if the customer is in queue 0, then its the departure event must be scheduled
	} else if (customer->queue_number_ == 1) {
		if (capacity[0] == 0) {
			EventNode *current_departure = new EventNode('D', customer, sim_time + customer->service_time_);
			event_list_add(current_departure);
		}
		//if the customer is in queue 1, then schedule its departure event only if the 
		//queue 0 is empty
		//else do not schedule the event
	} else if (customer->queue_number_ == 2) {
		if (capacity[0] == 0 && capacity[1] == 0) {
			EventNode *current_departure = new EventNode('D', customer, sim_time + customer->service_time_);
			event_list_add(current_departure);
		}
		//if the customer is in queue 2, then schedule its departure event only if the 
		//queue 0 and queue 1 are empty
	} else if (customer->queue_number_ == 3) {
		if (capacity[0] == 0 && capacity[1] == 0 && capacity[2] == 0) {
			EventNode *current_departure = new EventNode('D', customer, sim_time + customer->service_time_);
			event_list_add(current_departure);
		}
		//if the customer is in queue 3, then schedule its departure event only if the
		//queue 0, queue 1 and queue 2 are empty
	}
}

//process an arrival event
void PriorityNPSimulation::process_arrival(EventNode *next_event) {
	//generate the next inter-arrival time
	Ta = expntl(1/lambda, kSeed_arrival);
	Ts = expntl(1/mu, kSeed_service);
	//set the simulation time to the time of the next event
	sim_time = next_event->time_;
	//create a new customer for the next arrival and add it to the event list
	//cout << "Process arrival = " << next_event->customer_->id_ << endl;
	//cout << "queue number = " << next_event->customer_->queue_number_ << endl;
	if (next_event->customer_->queue_number_ == 0) {
		if (capacity[0] < kcpu/4) {
			arrival_count++;
			customer_queue_1.push_back(next_event->customer_);
			capacity[0]++;
			if (capacity[0] == 1) {
				schedule_departure(customer_queue_1.front());
			}
			Customer *customer = new Customer(total_arrival_count++, sim_time + Ta, Ts, 0, 0, false, false);
	//		cout << "customer id = " << customer->id_ << endl;
			init_customer_queue(customer);
			customer_list.push_back(customer);
			EventNode *next_arrival = new EventNode('A', customer, sim_time + Ta);
			event_list_add(next_arrival);
		} else {
			next_event->customer_->lost_ = true;
			next_event->customer_->serviced_ = false;
			EventNode *lost_arrival = new EventNode('L', next_event->customer_, sim_time);
			event_list_add(lost_arrival);
		}
	} else if (next_event->customer_->queue_number_ == 1) {
		if (capacity[1] < kcpu/4) {
			arrival_count++;
			customer_queue_2.push_back(next_event->customer_);
			capacity[1]++;
			if (capacity[1] == 1 && customer_queue_1.empty()) {
				schedule_departure(customer_queue_2.front());
			}
			Customer *customer = new Customer(total_arrival_count++, sim_time + Ta, Ts, 0, 0, false, false);
			init_customer_queue(customer);
			customer_list.push_back(customer);
			EventNode *next_arrival = new EventNode('A', customer, sim_time + Ta);
			event_list_add(next_arrival);
		} else {
			next_event->customer_->lost_ = true;
			next_event->customer_->serviced_ = false;
			EventNode *lost_arrival = new EventNode('L', next_event->customer_, sim_time);
			event_list_add(lost_arrival);
		}
	} else if (next_event->customer_->queue_number_ == 2) {
		if (capacity[2] < kcpu/4) {
			arrival_count++;
			customer_queue_3.push_back(next_event->customer_);
			capacity[2]++;
			if (capacity[2] == 1 && customer_queue_1.empty() &&
				customer_queue_2.empty()) {
				schedule_departure(customer_queue_3.front());
			}
			Customer *customer = new Customer(total_arrival_count++, sim_time + Ta, Ts, 0, 0, false, false);
			init_customer_queue(customer);
			customer_list.push_back(customer);
			EventNode *next_arrival = new EventNode('A', customer, sim_time + Ta);
			event_list_add(next_arrival);
		} else {
			next_event->customer_->lost_ = true;
			next_event->customer_->serviced_ = false;
			EventNode *lost_arrival = new EventNode('L', next_event->customer_, sim_time);
			event_list_add(lost_arrival);
		}
	} else if (next_event->customer_->queue_number_ == 3) {
		if (capacity[3] < kcpu/4) {
			arrival_count++;
			customer_queue_4.push_back(next_event->customer_);
			capacity[3]++;
			if (capacity[3] == 1 && customer_queue_1.empty() &&
				customer_queue_2.empty() && 
				customer_queue_3.empty()) {
				schedule_departure(customer_queue_4.front());
			}
			Customer *customer = new Customer(total_arrival_count++, sim_time + Ta, Ts, 0, 0, false, false);
			init_customer_queue(customer);
			customer_list.push_back(customer);
			EventNode *next_arrival = new EventNode('A', customer, sim_time + Ta);
			event_list_add(next_arrival);
		} else {
			next_event->customer_->lost_ = true;
			next_event->customer_->serviced_ = false;
			EventNode *lost_arrival = new EventNode('L', next_event->customer_, sim_time);
			event_list_add(lost_arrival);
		}
	}
}

//process the lost event
void PriorityNPSimulation::process_lost(EventNode *next_event) {
	//generate the next inter-arrival time
	Ta = expntl(1/lambda, kSeed_arrival);
	//set the simulation time to the time of the next event
	sim_time = next_event->time_;
	//create a new customer for the next arrival and add it to the event list
	Customer *customer = new Customer(total_arrival_count++, sim_time + Ta, 0, 0, 0, false, false); 
	customer_list.push_back(customer);
	//if the queue is not full, schedule the next event with the newly created customer
	//note that we since the customore for next_event is lost, we do not add it to the queue
	//else
	//this customer is also lost and cannot be serviced
	//create a new lost event and add it to the event list
	double selector = 1.0 * ran0(&kSeed_selector);
	if (selector <= 0.25) {
		customer->queue_number_ = 0;
		customer->queue_ = &customer_queue_1;
		if (capacity[0] < kcpu/4) {
			EventNode *next_arrival = new EventNode('A', customer, sim_time + Ta);
			event_list_add(next_arrival);
		} else {
			customer->lost_ = true;
			customer->serviced_ = false;
			EventNode *lost_arrival = new EventNode('L', customer, sim_time + Ta);
			event_list_add(lost_arrival);
		}
	} else if (selector <= 0.5) {
		customer->queue_number_ = 1;
		customer->queue_ = &customer_queue_2;
		if (capacity[1] < kcpu/4) {
			EventNode *next_arrival = new EventNode('A', customer, sim_time + Ta);
			event_list_add(next_arrival);
		} else {
			customer->lost_ = true;
			customer->serviced_ = false;
			EventNode *lost_arrival = new EventNode('L', customer, sim_time + Ta);
			event_list_add(lost_arrival);
		}
	} else if (selector <= 0.75) {
		customer->queue_number_ = 2;
		customer->queue_ = &customer_queue_3;
		if (capacity[2] < kcpu/4) {
			EventNode *next_arrival = new EventNode('A', customer, sim_time + Ta);
			event_list_add(next_arrival);
		} else {
			customer->lost_ = true;
			customer->serviced_ = false;
			EventNode *lost_arrival = new EventNode('L', customer, sim_time + Ta);
			event_list_add(lost_arrival);
		}
	} else {
		customer->queue_number_ = 3;
		customer->queue_ = &customer_queue_4;
		if (capacity[3] < kcpu/4) {
			EventNode *next_arrival = new EventNode('A', customer, sim_time + Ta);
			event_list_add(next_arrival);
		} else {
			customer->lost_ = true;
			customer->serviced_ = false;
			EventNode *lost_arrival = new EventNode('L', customer, sim_time + Ta);
			event_list_add(lost_arrival);
		}
	}
}

void PriorityNPSimulation::process_departure(EventNode *next_event) {
 	//get the customer corresponding to next_event
	//get the queue corresponding to the customer
	//if the queue is not empty, process the departure of the customer from that queue
	//if the queue is 0, and there are remaining customers in the queue, schedule departure events for the next customer, otherwise, schedule the first customer in the first non-empty queue 1, 2, 3
	//if the queue is 1, and there are customers in the queue 0, schedule departure events for the first of these customers, otherwise, if queue 1 is not empty, then schedule a departure event for the next customer in queue 1, otherwise, schedule the first customer in the first non-empty queue 2, 3
	//....and so on for queues 2 and 3 respectively
	//print_queues();
	//event_list_print();
	if (! next_event->customer_->queue_->empty()) {
		/*if (next_event->customer_->queue_->front()->id_ != next_event->customer_->id_) {
			cout << "caught : " 
				 << next_event->customer_->queue_->front()->id_ << ", "
				 << next_event->customer_->id_ << endl;
		}*/
		Customer *departing_customer = next_event->customer_;
		sim_time = next_event->time_;
		departing_customer->departure_time_ = sim_time;
		departing_customer->serviced_ = true;
		departing_customer->lost_ = false;
		departing_customer->departure_number_ = ++departure_count;
		//cout << "customer id = " << departing_customer->id_
		//	 << ", departure count = " << departure_count 
		//	 << ", queue = " << departing_customer->queue_number_ << endl;
		departing_customer->queue_->pop_front();
		departing_customer->customers_after_ = departing_customer->queue_->size();
		capacity[departing_customer->queue_number_]--;
		serve_count++;
		if (capacity[0] > 0) {
			EventNode *current = find_event_for_customer(customer_queue_1.front(), 'D');
			if (current == NULL) {
				schedule_departure(customer_queue_1.front());
			} else {
				event_list_remove(current);
				schedule_departure(customer_queue_1.front());
			}
		} else if (capacity[1] > 0) {
			EventNode *current = find_event_for_customer(customer_queue_2.front(), 'D');
			if (current == NULL) {
				schedule_departure(customer_queue_2.front());
			} else {
				event_list_remove(current);
				schedule_departure(customer_queue_2.front());
			}
		} else if (capacity[2] > 0) {
			EventNode *current = find_event_for_customer(customer_queue_3.front(), 'D');
			if (current == NULL) {
				schedule_departure(customer_queue_3.front());
			} else {
				event_list_remove(current);
				schedule_departure(customer_queue_3.front());
			}
		} else if (capacity[3] > 0) {
			EventNode *current = find_event_for_customer(customer_queue_4.front(), 'D');
			if (current == NULL) {
				schedule_departure(customer_queue_4.front());
			} else {
				event_list_remove(current);
				schedule_departure(customer_queue_4.front());
			}
		}
		/*if (next_event->customer_->queue_number_ == 0) {
			if (capacity[0] > 0) {
				schedule_departure(customer_queue_1.front());
			} else if (capacity[1] > 0) {
				schedule_departure(customer_queue_2.front());
			} else if (capacity[2] > 0) {
				schedule_departure(customer_queue_3.front());
			} else if (capacity[3] > 0) {
				schedule_departure(customer_queue_4.front());
			}
		} else if (next_event->customer_->queue_number_ == 1) {
			if (capacity[0] > 0) {
				schedule_departure(customer_queue_1.front());
			} else if (capacity[1] > 0) {
				schedule_departure(customer_queue_2.front());
			}
		} else if (next_event->customer_->queue_number_ == 2) {
			if (capacity[0] > 0) {
				schedule_departure(customer_queue_1.front());
			} else if (capacity[1] > 0) {
				schedule_departure(customer_queue_2.front());
			} else if (capacity[2] > 0) {
				schedule_departure(customer_queue_3.front());
			} 
		} else if (next_event->customer_->queue_number_ == 3) {
			if (capacity[0] > 0) {
				schedule_departure(customer_queue_1.front());
			} else if (capacity[1] > 0) {
				schedule_departure(customer_queue_2.front());
			} else if (capacity[2] > 0) {
				schedule_departure(customer_queue_3.front());
			} else if (capacity[3] > 0) {
				schedule_departure(customer_queue_4.front());
			}
		}*/
	}
}

void PriorityNPSimulation::init_customer_queue(Customer *customer) {

	double selector = 1.0 * ran0(&kSeed_selector);
	//cout << "selector = " << selector << endl;
	if (selector <= 0.25) {
		customer->queue_number_ = 0;
		customer->queue_ = &customer_queue_1;
	} else if (selector <= 0.5) {
		customer->queue_number_ = 1;
		customer->queue_ = &customer_queue_2;
	} else if (selector <= 0.75) {
		customer->queue_number_ = 2;
		customer->queue_ = &customer_queue_3;
	} else {
		customer->queue_number_ = 3;
		customer->queue_ = &customer_queue_4;
	}
}

void PriorityNPSimulation::calculate_statistics() {
	double overall_avg_service_time = 0, overall_avg_waiting_time = 0;
	double overall_avg_system_time = 0;
	uint32_t customers_lost[4] = {0}, customers_serviced[4] = {0};
	uint32_t total_loss_count = 0;
	//cout << "customer list size = " << customer_list.size() << endl;
	Customer *current;
	for (uint32_t index = 0; index < customer_list.size(); ++index) {
		current = customer_list[index];
		current->waiting_time_ = current->departure_time_ -
								 current->service_time_ -
								 current->arrival_time_;

		if (current->departure_number_ == 0) {
			total_loss_count++;
		}
		if (current->departure_number_ > 1000) {
			customers_serviced[current->queue_number_]++;
			queue_avg_service_time[current->queue_number_] += 
				current->service_time_;
			queue_avg_waiting_time[current->queue_number_] +=
				current->waiting_time_;
			overall_avg_service_time += current->service_time_;
			overall_avg_waiting_time += current->waiting_time_;
			overall_avg_system_time += (current->departure_time_ -
										 current->arrival_time_);
		//	customer_list[index]->print();
		}
		if (current->lost_) {
			customers_lost[current->queue_number_]++;
		}
		//customer_list[index]->print();
	}
	overall_avg_system_time /= (c-1000);
	overall_avg_service_time /= (c - 1000);
	overall_avg_waiting_time /= (c - 1000);
	clr = 1.0 * total_loss_count / (c - 1000);

	queue_clr[0] = 1.0 * customers_lost[0] / (customers_lost[0] + customers_serviced[0]);
	queue_clr[1] = 1.0 * customers_lost[1] / (customers_lost[1] + customers_serviced[1]);
	queue_clr[2] = 1.0 * customers_lost[2] / (customers_lost[2] + customers_serviced[2]);
	queue_clr[3] = 1.0 * customers_lost[3] / (customers_lost[3] + customers_serviced[3]);
	for (uint32_t index = 0; index < 4; ++index) {
		//cout << customers_serviced[index] << endl;
		queue_avg_service_time[index] /= customers_serviced[index];
		queue_avg_waiting_time[index] /= customers_serviced[index];
	}

	avg_system_time = overall_avg_system_time;
	avg_service_time = overall_avg_service_time;
	avg_waiting_time = overall_avg_waiting_time;
}

void PriorityNPSimulation::print_queues() {
	cout << "QUEUES START" << endl;
	for (uint32_t index = 0; index < customer_queue_1.size(); ++index) {
		cout << customer_queue_1[index]->id_ << " ";
	}
	cout << endl; 
	for (uint32_t index = 0; index < customer_queue_2.size(); ++index) {
		cout << customer_queue_2[index]->id_ << " ";
	}
	cout << endl; 
	for (uint32_t index = 0; index < customer_queue_3.size(); ++index) {
		cout << customer_queue_3[index]->id_ << " ";
	}
	cout << endl; 
	for (uint32_t index = 0; index < customer_queue_4.size(); ++index) {
		cout << customer_queue_4[index]->id_ << " ";
	}
	cout << endl; 
	cout << "QUEUES END" << endl;
}
